#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <winsock.h>
#include <direct.h>
#include <pthread.h>

#include "ukonton.h"
#include "modules.h"

#define BUFSIZE 8096

enum logtypes{
	FATAL,
	SORRY,
	LOG
};

struct {
	char *ext;
	char *filetype;
} extensions [] = {
	{"htm", "text/html" },  
	{"html","text/html" },  
	{"txt", "text/plain" },
	{"gif", "image/gif" },  
	{"jpg", "image/jpeg"}, 
	{"jpeg","image/jpeg"},
	{"png", "image/png" },  
	{"zip", "image/zip" },  
	{"gz",  "image/gz"  },  
	{"tar", "image/tar" },  
	{0,0} };

struct targcont{
	int fd;
	int hit;
};

//GLOBALS
modulesNode *mods=NULL;
extern ukontonModule module_index;
extern ukontonModule module_core;

void log(int type,char *s1,char *s2,int num){
	FILE *fd ;
	char logbuffer[BUFSIZE*2];

	switch (type) {
	case FATAL: (void)sprintf(logbuffer,"FATAL: %s:%s Errno=%d exiting pid=%d",s1, s2, errno,GetCurrentThreadId()/*getpid()*/); break;
	case LOG: (void)sprintf(logbuffer,"INFO: %s:%s:%d",s1, s2,num); break;
	case SORRY: 
		(void)sprintf(logbuffer, "<HTML><BODY><H1>nkonton Web Server Sorry: %s %s</H1></BODY></HTML>\r\n", s1, s2);
		(void)send(num,logbuffer,strlen(logbuffer),0);
		closesocket(num);
		(void)sprintf(logbuffer,"SORRY: %s:%s",s1, s2); 
		break;
	
	}	
	if((fd = fopen("web.log","a")) > 0) {
		(void)fwrite(logbuffer,sizeof(char),strlen(logbuffer),fd);
		(void)fwrite("\n",sizeof(char),1,fd);      
		(void)fclose(fd);
	}
	if(type == FATAL) exit(3);
	else if(type == SORRY) pthread_exit((void*)3);
}

modulesNode *list_add(modulesNode **p,modType *i) {
    modulesNode *n = malloc(sizeof(modulesNode)); /* you normally don't cast a return value for malloc */
    n->next = *p;
    *p = n;
    n->mod = i;
    return n;
}
 
int list_remove(modulesNode **p) { /* remove head */
	modulesNode *n;
    if (*p != NULL) {
        n = *p;
        *p = (*p)->next;
        free(n);
		return 1;
    }else return 0;
}
 
modulesNode **list_search(modulesNode **n,char *i) {
    while (*n!=NULL && (*n)->mod!=NULL) {
		if(!strcmp((*n)->mod->module->name,i)) {
			return n;
		}
		if(&(*n)->next)
			n = &(*n)->next;
    }
    return NULL;
}

void list_print(modulesNode *n) {
    if (n == NULL) {
        printf("list is empty\n");
    }
    while (n != NULL) {
		printf("print n:%p next:%p mod:%d name:%s\n", n, n->next, n->mod, n->mod->module->name);
        n = n->next;
    }
}

char* runmod(modulesNode *mod,char *modname,char *args){
	modulesNode **nmod=NULL;
	if(nmod=list_search(&mod,modname)){
		return (*nmod)->mod->module->run(args);
	}else return 0;
}

void cleanup(void){do{if(mods)FreeLibrary(mods->mod->lib);}while(list_remove(&mods));}

void *web(void* arg){
	int fd, hit;
	FILE *file_fd;
	int j, buflen, len;
	long i, ret;
	char *fstr,*astr;
	static char buffer[BUFSIZE+1];
	struct targcont *args;

	args=(struct targcont*)arg;
	fd=args->fd;
	hit=args->hit;

	ret =recv(fd,buffer,BUFSIZE,0);
	if(ret == 0 || ret == -1) {
		log(SORRY,"failed to read browser request","",fd);
	}
	if(ret > 0 && ret < BUFSIZE)buffer[ret]=0; else buffer[0]=0;

	for(i=0;i<ret;i++)
		if(buffer[i] == '\r' || buffer[i] == '\n')
			buffer[i]='*';
	log(LOG,"request",buffer,hit);

	if( strncmp(buffer,"GET ",4) && strncmp(buffer,"get ",4) )
		log(SORRY,"Only simple GET operation supported",buffer,fd);

	for(i=4;i<BUFSIZE;i++) {
		if(buffer[i] == ' ') {
			buffer[i] = 0;
			break;
		}
	}

	for(j=0;j<i-1;j++)
		if(buffer[j] == '.' && buffer[j+1] == '.')
			log(SORRY,"relative directory (..) path names not supported",buffer,fd);

	if( !strncmp(&buffer[0],"GET /\0",6) || !strncmp(&buffer[0],"get /\0",6) )
		(void)strcpy(buffer,"GET /index");

	astr=strtok(&buffer[5],"/");
	astr=strtok(NULL,"/");
	fstr=runmod(mods,&buffer[5],astr);
	if(fstr==0)
		log(SORRY, "failed to run module",&buffer[5],fd);
	log(LOG,"SEND",&buffer[5],hit);

	(void)sprintf(buffer,"HTTP/1.0 200 OK\r\nContent-Type: %s\r\n\r\n",extensions[0].filetype);
	(void)send(fd,buffer,strlen(buffer),0);
	(void)send(fd,fstr,strlen(fstr),0);
	closesocket(fd);
	free(fstr);
	return 0;

	/*buflen=(int)strlen(buffer);
	fstr = (char *)0;
	for(i=0;extensions[i].ext != 0;i++) {
		len = (int)strlen(extensions[i].ext);
		if( !strncmp(&buffer[buflen-len], extensions[i].ext, len)) {
			fstr =extensions[i].filetype;
			break;
		}
	}
	if(fstr == 0) log(SORRY,"file extension type not supported",buffer,fd);

	if(( file_fd = fopen(&buffer[5],"r")) == 0)
		log(SORRY, "failed to open file",&buffer[5],fd);

	log(LOG,"SEND",&buffer[5],hit);

	(void)sprintf(buffer,"HTTP/1.0 200 OK\r\nContent-Type: %s\r\n\r\n", fstr);
	(void)send(fd,buffer,strlen(buffer),0);

	while (	(ret = fread(buffer,sizeof(char),BUFSIZE,file_fd)) > 0 ) {
		(void)send(fd,buffer,ret,0);
	}
	closesocket(fd);
	return 0;*/
}

int main(int argc, char **argv)
{
	int nCode;
	int i, port, hit;
	size_t length;
	int listenfd, socketfd;
	static struct sockaddr_in cli_addr;
	static struct sockaddr_in serv_addr;
	WSADATA wsaData;
	pthread_t thread;
	struct targcont targ;
	modType *modt=NULL;

	int counter;
	wchar_t tmp[20];
	char *tok;
	FILE *fcfg;
	char buf[8192];

	memset(&buf,0,8192);

	if( argc < 3  || argc > 3 || !strcmp(argv[1], "-?") ) {
		(void)printf("usage: nkonton Port-Number Top-Directory\n\n"
			"\tnkonton only servers out file/web pages with extensions named below\n"
			"\t and only from the named directory or its sub-directories.\n"
			"\tOnly Supports:");
		for(i=0;extensions[i].ext != 0;i++)
			(void)printf(" %s",extensions[i].ext);
		(void)printf("\n\tNot Supported: URLs including \"..\", Java, Javascript, CGI\n");
		exit(0);
	}

	// ADD COMPILED IN MODULES
	modt=malloc(sizeof(modType));
	modt->lib=NULL;
	modt->module=&module_index;
	list_add(&mods,modt);
	modt=malloc(sizeof(modType));
	modt->lib=NULL;
	modt->module=&module_core;
	list_add(&mods,modt);

	// ADD DYNAMIC LOADED MODULES
	fcfg=fopen("modules.cfg","r");
	if(fcfg){
		if(fread(&buf,sizeof(char),8192,fcfg)){
			fclose(fcfg);
			tok=strtok(buf,"\n");
			while(tok){
				modt=modload(tok);
				if(!modt){
					(void)printf("WARNING: Can't load module '%s'\n",tok);
					//exit(2);
				}else{
					(void)printf("MODULE: '%s' loaded!\n",tok);
					list_add(&mods,modt);
					mods->mod->module->init();
				}
				tok=strtok(NULL,"\n");
			}
			atexit(cleanup);
		}
	}else{
		printf("modules.cfg -> read error\n");
		exit(7);
	}

	if(_chdir(argv[2]) == -1){ 
		(void)printf("FATAL: Can't Change to directory %s\n",argv[2]);
		exit(4);
	}

    if((nCode = WSAStartup(MAKEWORD(1, 1), &wsaData)) != 0) {
		(void)printf("WSAStartup() returned error code %i.\n",nCode);
        exit(5);
    }

	log(LOG,"nkonton starting",argv[1],GetCurrentThreadId());
	if((listenfd=(int)socket(AF_INET, SOCK_STREAM,0))<0)
		log(FATAL, "system call","socket",0);
	port=atoi(argv[1]);
	if(port < 0 || port >65535)
		log(FATAL,"Invalid port number (try 1-65535)",argv[1],0);
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_addr.sin_port = htons(port);
	if(bind(listenfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr))<0)
		log(FATAL,"system call","bind",0);
	if(listen(listenfd,64)<0)
		log(FATAL,"system call","listen",0);

	for(hit=1;;hit++){
		length=sizeof(cli_addr);
		if((socketfd=(int)accept(listenfd,(struct sockaddr *)&cli_addr,(int*)&length))<0)
			log(FATAL,"system call","accept",0);
		targ.fd = socketfd;
		targ.hit = hit;
		if(!pthread_create(&thread,NULL,&web,&targ));else
			log(FATAL,"system call","pthread_create",0);
	}

    WSACleanup();
}
